// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved

#include "Private.h"
#include "Globals.h"
#include "BaseWindow.h"
#include "ScrollBarWindow.h"

//////////////////////////////////////////////////////////////////////
//
// CScrollBarWindowFactory class.
//
//////////////////////////////////////////////////////////////////////
CScrollBarWindowFactory* CScrollBarWindowFactory::_instance;

CScrollBarWindowFactory::CScrollBarWindowFactory() {
	_instance = nullptr;
}

CScrollBarWindowFactory* CScrollBarWindowFactory::Instance() {
	if (nullptr == _instance) {
		_instance = new (std::nothrow) CScrollBarWindowFactory();
	}

	return _instance;
}

CScrollBarWindow* CScrollBarWindowFactory::MakeScrollBarWindow(SHELL_MODE shellMode) {
	CScrollBarWindow* pScrollBarWindow = nullptr;

	switch (shellMode) {
	case STOREAPP:
		pScrollBarWindow = new (std::nothrow) CScrollBarWindow();
		break;

	case DESKTOP:
		pScrollBarWindow = new (std::nothrow) CScrollBarNullWindow();
		break;

	default:
		pScrollBarWindow = new (std::nothrow) CScrollBarNullWindow();
		break;
	}
	return pScrollBarWindow;
}

void CScrollBarWindowFactory::Release() {
	if (_instance) {
		delete _instance;
		_instance = nullptr;
	}
}

//////////////////////////////////////////////////////////////////////
//
// CScrollBarWindow class.
//
//////////////////////////////////////////////////////////////////////

//+---------------------------------------------------------------------------
//
// ctor
//
//----------------------------------------------------------------------------

CScrollBarWindow::CScrollBarWindow() {
	_pBtnUp = nullptr;
	_pBtnDn = nullptr;

	_scrollDir = SCROLL_NONE_DIR;

	_sizeOfScrollBtn.cx = GetSystemMetrics(SM_CXVSCROLL) * 2;
	_sizeOfScrollBtn.cy = GetSystemMetrics(SM_CYVSCROLL) * 2;
}

//+---------------------------------------------------------------------------
//
// dtor
//
//----------------------------------------------------------------------------

CScrollBarWindow::~CScrollBarWindow() {
	if (_pBtnUp) {
		delete _pBtnUp;
		_pBtnUp = nullptr;
	}
	if (_pBtnDn) {
		delete _pBtnDn;
		_pBtnDn = nullptr;
	}
}

//+---------------------------------------------------------------------------
//
// _Create
//
//----------------------------------------------------------------------------

BOOL CScrollBarWindow::_Create(ATOM atom, DWORD dwExStyle, DWORD dwStyle, CBaseWindow* pParent, int wndWidth, int wndHeight) {
	if (!CBaseWindow::_Create(atom, dwExStyle, dwStyle, pParent, wndWidth, wndHeight)) {
		return FALSE;
	}

	_pBtnUp = new (std::nothrow) CScrollButtonWindow(dwStyle & WS_HSCROLL ? DFCS_SCROLLLEFT : DFCS_SCROLLUP);
	if (_pBtnUp == nullptr) {
		return FALSE;
	}

	_pBtnUp->_Create(NULL, 0, 0, this);
	_pBtnUp->_SetUIWnd(_GetUIWnd());

	_pBtnDn = new (std::nothrow) CScrollButtonWindow(dwStyle & WS_HSCROLL ? DFCS_SCROLLRIGHT : DFCS_SCROLLDOWN);
	if (_pBtnDn == nullptr) {
		delete _pBtnUp;
		_pBtnUp = nullptr;
		return FALSE;
	}

	_pBtnDn->_Create(NULL, 0, 0, this);
	_pBtnDn->_SetUIWnd(_GetUIWnd());

	return TRUE;
}

//+---------------------------------------------------------------------------
//
// _WindowProcCallback
//
// Scrollbar window proc.
//----------------------------------------------------------------------------

LRESULT CALLBACK CScrollBarWindow::_WindowProcCallback(_In_ HWND wndHandle, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam) {
	switch (uMsg) {
	case WM_PAINT:
	{
		HDC dcHandle = nullptr;
		PAINTSTRUCT ps;

		dcHandle = BeginPaint(wndHandle, &ps);

		// paint itself at first
		_OnPaint(dcHandle, &ps);

		// paint all children
		_pBtnUp->_OnPaint(dcHandle, &ps);
		_pBtnDn->_OnPaint(dcHandle, &ps);

		EndPaint(wndHandle, &ps);
	}
	return 0;
	}

	return DefWindowProc(wndHandle, uMsg, wParam, lParam);
}

//+---------------------------------------------------------------------------
//
// _OnPaint
//
//----------------------------------------------------------------------------

void CScrollBarWindow::_OnPaint(_In_ HDC dcHandle, _In_ PAINTSTRUCT* pps) {
	HBRUSH hBrush = nullptr;
	CBaseWindow* pUIWnd = _GetUIWnd();

	if (pUIWnd != nullptr && pUIWnd->_GetWnd()) {
		hBrush = (HBRUSH)DefWindowProc(pUIWnd->_GetWnd(), WM_CTLCOLORSCROLLBAR, (WPARAM)dcHandle, (LPARAM)pUIWnd->_GetWnd());
	}

	if (hBrush == nullptr) {
		hBrush = GetSysColorBrush(COLOR_SCROLLBAR);
	}

	FillRect(dcHandle, &pps->rcPaint, hBrush);
}

//+---------------------------------------------------------------------------
//
// _OnLButtonDown
//
//----------------------------------------------------------------------------

void CScrollBarWindow::_OnLButtonDown(POINT pt) {
	_StartCapture();

	RECT rc = { 0, 0, 0, 0 };

	_pBtnUp->_GetClientRect(&rc);
	if (PtInRect(&rc, pt)) {
		_pBtnUp->_OnLButtonDown(pt);
	} else {
		_pBtnDn->_GetClientRect(&rc);
		if (PtInRect(&rc, pt)) {
			_pBtnDn->_OnLButtonDown(pt);
		}
	}
}

//+---------------------------------------------------------------------------
//
// _OnLButtonUp
//
//----------------------------------------------------------------------------

void CScrollBarWindow::_OnLButtonUp(POINT pt) {
	if (_IsCapture()) {
		CBaseWindow* pUIWnd = _GetTopmostUIWnd();
		if (pUIWnd) {
			CBaseWindow* pCapture = pUIWnd->_GetCaptureObject();
			if (pCapture && pCapture != this) {
				pCapture->_OnLButtonUp(pt);
			}
		}
	} else {
		RECT rc = { 0, 0, 0, 0 };

		_pBtnUp->_GetClientRect(&rc);
		if (PtInRect(&rc, pt)) {
			_pBtnUp->_OnLButtonUp(pt);
		} else {
			_pBtnDn->_GetClientRect(&rc);
			if (PtInRect(&rc, pt)) {
				_pBtnDn->_OnLButtonUp(pt);
			}
		}
	}

	if (_IsCapture()) {
		_EndCapture();
	}
	if (_IsTimer()) {
		_EndTimer();
	}

	_scrollDir = SCROLL_NONE_DIR;
	_InvalidateRect();
}

//+---------------------------------------------------------------------------
//
// _OnMouseMove
//
//----------------------------------------------------------------------------

void CScrollBarWindow::_OnMouseMove(POINT pt) {
	RECT rc = { 0, 0, 0, 0 };

	_pBtnUp->_GetClientRect(&rc);
	if (PtInRect(&rc, pt)) {
		_pBtnUp->_OnMouseMove(pt);
	} else {
		_pBtnDn->_GetClientRect(&rc);
		if (PtInRect(&rc, pt)) {
			_pBtnDn->_OnMouseMove(pt);
		}
	}
}

//+---------------------------------------------------------------------------
//
// _OnOwnerWndMoved
//
//----------------------------------------------------------------------------

void CScrollBarWindow::_OnOwnerWndMoved(BOOL isResized) {
	isResized;

	if (IsWindow(_GetWnd()) && IsWindowVisible(_GetWnd())) {
		_AdjustWindowPos();
	}
}

//+---------------------------------------------------------------------------
//
// _Resize
//
//----------------------------------------------------------------------------

void CScrollBarWindow::_Resize(int x, int y, int cx, int cy) {
	CBaseWindow::_Resize(x, y, cx, cy);

	RECT rc = { 0, 0, 0, 0 };

	_GetBtnUpRect(&rc);
	_pBtnUp->_Resize(rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top);

	_GetBtnDnRect(&rc);
	_pBtnDn->_Resize(rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top);
}

//+---------------------------------------------------------------------------
//
// _Show
//
//----------------------------------------------------------------------------

void CScrollBarWindow::_Show(BOOL isShowWnd) {
	if (_IsWindowVisible() != isShowWnd) {
		CBaseWindow::_Show(isShowWnd);

		_pBtnUp->_Show(isShowWnd);
		_pBtnDn->_Show(isShowWnd);
	}
}

//+---------------------------------------------------------------------------
//
// _Enable
//
//----------------------------------------------------------------------------

void CScrollBarWindow::_Enable(BOOL isEnable) {
	if (_IsEnabled() != isEnable) {
		CBaseWindow::_Enable(isEnable);

		_pBtnUp->_Enable(isEnable);
		_pBtnDn->_Enable(isEnable);
	}
}

//+---------------------------------------------------------------------------
//
// _AdjustWindowPos
//
//----------------------------------------------------------------------------

void CScrollBarWindow::_AdjustWindowPos() {
	if (!IsWindow(_GetWnd())) {
		return;
	}

	RECT rc = { 0, 0, 0, 0 };
	CBaseWindow* pParent = _GetParent();
	if (pParent == nullptr) {
		return;
	}

	GetWindowRect(pParent->_GetWnd(), &rc);
	SetWindowPos(_GetWnd(), pParent->_GetWnd(),
		rc.left,
		rc.top,
		rc.right - rc.left,
		rc.bottom - rc.top,
		SWP_NOOWNERZORDER | SWP_NOACTIVATE);
}

//+---------------------------------------------------------------------------
//
// _SetScrollInfo
//
//----------------------------------------------------------------------------

void CScrollBarWindow::_SetScrollInfo(_In_ CScrollInfo* lpsi) {
	_scrollInfo = *lpsi;

	BOOL isEnable = (_scrollInfo.nMax > _scrollInfo.nPage);

	_Enable(isEnable);

	_scrollDir = SCROLL_NONE_DIR;

	_SetCurPos(_scrollInfo.nPos, -1);
}

//+---------------------------------------------------------------------------
//
// _GetScrollInfo
//
//----------------------------------------------------------------------------

void CScrollBarWindow::_GetScrollInfo(_Out_ CScrollInfo* lpsi) {
	*lpsi = _scrollInfo;
}

//+---------------------------------------------------------------------------
//
// _GetBtnUpRect
//
//----------------------------------------------------------------------------

BOOL CScrollBarWindow::_GetBtnUpRect(_Out_ RECT* prc) {
	RECT rc = { 0, 0, 0, 0 };

	_GetClientRect(&rc);

	if (prc == nullptr) {
		return FALSE;
	}

	prc->left = rc.left;
	prc->top = rc.top;
	prc->right = rc.right;
	prc->bottom = rc.top + min(_sizeOfScrollBtn.cy, (rc.bottom - rc.top) / 2);

	return TRUE;
}

//+---------------------------------------------------------------------------
//
// _GetBtnDnRect
//
//----------------------------------------------------------------------------

BOOL CScrollBarWindow::_GetBtnDnRect(_Out_ RECT* prc) {
	RECT rc = { 0, 0, 0, 0 };

	_GetClientRect(&rc);

	if (prc == nullptr) {
		return FALSE;
	}

	prc->left = rc.left;
	prc->top = rc.bottom - min(_sizeOfScrollBtn.cy, (rc.bottom - rc.top) / 2);
	prc->right = rc.right;
	prc->bottom = rc.bottom;

	return TRUE;
}

//+---------------------------------------------------------------------------
//
// _GetScrollArea
//
//----------------------------------------------------------------------------

void CScrollBarWindow::_GetScrollArea(_Out_ RECT* prc) {
	RECT rcBtnUp = { 0, 0, 0, 0 };
	RECT rcBtnDn = { 0, 0, 0, 0 };

	_GetBtnUpRect(&rcBtnUp);
	_GetBtnDnRect(&rcBtnDn);

	RECT rc = { 0, 0, 0, 0 };

	_GetClientRect(&rc);

	if (prc == nullptr) {
		return;
	}

	prc->left = rc.left;
	prc->top = rc.top + (rcBtnUp.bottom - rcBtnUp.top);
	prc->right = rc.right;
	prc->bottom = rc.bottom - (rcBtnDn.bottom - rcBtnDn.top);
}

//+---------------------------------------------------------------------------
//
// _SetCurPos
//
// param: dwSB - SB_xxx for WM_VSCROLL or WM_HSCROLL.
//    if -1 is specified, function doesn't send WM_xSCROLL message
//----------------------------------------------------------------------------

void CScrollBarWindow::_SetCurPos(int nPos, int dwSB) {
	int posMax = (_scrollInfo.nMax <= _scrollInfo.nPage) ? 0 : _scrollInfo.nMax - _scrollInfo.nPage;

	nPos = min(nPos, posMax);
	nPos = max(nPos, 0);

	_scrollInfo.nPos = nPos;

	if (_IsWindowVisible()) {
		_InvalidateRect();
	}

	if ((_IsCapture() && dwSB != -1) || (dwSB == SB_THUMBPOSITION)) {
		_NotifyCommand(WM_VSCROLL, dwSB, nPos);
	}
}

//////////////////////////////////////////////////////////////////////
//
// CScrollButtonWindow class.
//
//////////////////////////////////////////////////////////////////////

//+---------------------------------------------------------------------------
//
// _OnPaint
//
//----------------------------------------------------------------------------

void CScrollButtonWindow::_OnPaint(_In_ HDC dcHandle, _In_ PAINTSTRUCT* pps) {
	pps;

	RECT rc = { 0, 0, 0, 0 };

	_GetClientRect(&rc);

	DrawFrameControl(dcHandle, &rc, DFC_SCROLL, subTypeOfControl | typeOfControl | (!_IsEnabled() ? DFCS_INACTIVE : 0));
}

//+---------------------------------------------------------------------------
//
// _OnLButtonDown
//
//----------------------------------------------------------------------------

void CScrollButtonWindow::_OnLButtonDown(POINT pt) {
	CButtonWindow::_OnLButtonDown(pt);

	CScrollBarWindow* pParent = (CScrollBarWindow*)_GetParent();    // more secure w/ dynamic_cast
	if (pParent == nullptr) {
		return;
	}

	switch (subTypeOfControl) {
	case DFCS_SCROLLDOWN:
		_NotifyCommand(WM_VSCROLL, SB_LINEDOWN, 0);
		pParent->_ShiftLine(+1, FALSE);
		break;
	case DFCS_SCROLLUP:
		_NotifyCommand(WM_VSCROLL, SB_LINEUP, 0);
		pParent->_ShiftLine(-1, FALSE);
		break;
	}

	_StartTimer(_GetScrollDelay());
}

//+---------------------------------------------------------------------------
//
// _OnLButtonUp
//
//----------------------------------------------------------------------------

void CScrollButtonWindow::_OnLButtonUp(POINT pt) {
	CButtonWindow::_OnLButtonUp(pt);

	_InvalidateRect();

	if (_IsTimer()) {
		_EndTimer();
	}
}

//+---------------------------------------------------------------------------
//
// _OnTimer : Speed up page Down/Up while holding Down/Up Button
//
//----------------------------------------------------------------------------

void CScrollButtonWindow::_OnTimer() {
	POINT pt = { 0, 0 };
	CScrollBarWindow* pParent = (CScrollBarWindow*)_GetParent();    // more secure w/ dynamic_cast
	if (pParent == nullptr) {
		return;
	}

	// start another faster timer
	_StartTimer(_GetScrollSpeed());

	GetCursorPos(&pt);
	MapWindowPoints(HWND_DESKTOP, pParent->_GetWnd(), &pt, 1);

	RECT rc = { 0, 0, 0, 0 };

	_GetClientRect(&rc);

	if (PtInRect(&rc, pt)) {
		switch (subTypeOfControl) {
		case DFCS_SCROLLDOWN:
			_NotifyCommand(WM_VSCROLL, SB_LINEDOWN, 0);
			pParent->_ShiftLine(+1, FALSE);
			break;
		case DFCS_SCROLLUP:
			_NotifyCommand(WM_VSCROLL, SB_LINEUP, 0);
			pParent->_ShiftLine(-1, FALSE);
			break;
		}
	}
}